package com.reger.l2cache.core;

import java.util.Iterator;
import java.util.Map;
import java.util.Map.Entry;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.util.SerializationUtils;

public class LocalCache extends Cache implements DisposableBean, Runnable {

	private static final Logger log = LoggerFactory.getLogger(LocalCache.class);

	/**
	 * 存放方法返回
	 */
	private final Map<String, byte[]> cache = new ConcurrentHashMap<String, byte[]>();
	/**
	 * 存放方法缓存超时状态
	 */
	private final Map<String, Long> cacheTimeOut = new ConcurrentHashMap<String, Long>();
	/**
	 * 存放方法调用中状态,也就是锁
	 */
	private final Map<String, Long> invokLock = new ConcurrentHashMap<String, Long>();

	private final Thread clearThread;
	private static boolean INTERRUPTED = false;

	public LocalCache() {
		this(null);
	}

	public LocalCache(Cache cache) {
		super(cache);
		clearThread = new Thread(this, "自动清理本地缓存的线程");
		clearThread.start();
	}

	@Override
	public void destroy() throws Exception {
		INTERRUPTED = true;
	}

	@Override
	public void run() {
		while (!INTERRUPTED) {
			try {
				int num = this.clearTimeCache();
				if (num == 0) { // 如果本次缓存清理完毕，就暂停1秒后再继续清理
					TimeUnit.SECONDS.sleep(1);
				}
			} catch (InterruptedException e) {
				INTERRUPTED = true;
				log.info("清除过期缓存key的线程退出", e);
			} catch (Throwable e) {
				log.warn("清除过期缓存key异常", e);
			}
		}
	}

	@Override
	protected synchronized boolean tryLock(String cacheKey, long timeOut) {
		log.debug("试图拿到本地锁{}", cacheKey);
		if (invokLock.containsKey(cacheKey) && invokLock.get(cacheKey) <= timeOut) {
			return false;
		}
		invokLock.put(cacheKey, timeOut);
		return true;
	}

	@Override
	protected synchronized void unLock(String cacheKey) {
		log.debug("试图释放本地锁{}", cacheKey);
		invokLock.remove(cacheKey);
	}

	@Override
	protected synchronized boolean exist(String cacheKey) {
		log.debug("判断缓存中是否有cacheKey: {}", cacheKey);
		if (cacheTimeOut.containsKey(cacheKey)) {
			Long timeOut = cacheTimeOut.get(cacheKey);
			if (timeOut == null)
				return false;
			return System.currentTimeMillis() < timeOut;
		}
		return false;
	}

	@Override
	@SuppressWarnings("unchecked")
	protected Relust<Object> get(String cacheKey, Class<?> returnType) {
		log.debug("试图拿到本地数据{}", cacheKey);
		if (cache.containsKey(cacheKey)) {
			byte[] bytes = cache.get(cacheKey);
			if (bytes == null)
				return null;
			log.debug("从本地缓存中命中cacheKey: {}", cacheKey);
			return (Relust<Object>) SerializationUtils.deserialize(bytes);
		}
		return null;
	}

	@Override
	protected void put(String cacheKey, Object value, long timeOut) {
		log.debug("结果集写入本地缓存cacheKey: {}", cacheKey);
		long timeOut_ = (long) Math.ceil(timeOut / 3.0);
		timeOut_ = timeOut_ < 10 ? 10 : timeOut_ > 800 ? 800 : timeOut_;
		cacheTimeOut.put(cacheKey, timeOut_ + System.currentTimeMillis());
		if (value != null) {
			cache.put(cacheKey, SerializationUtils.serialize(new Relust<Object>(value)));
		} else {
			cache.put(cacheKey, Relust.NULL_BYTE);
		}
	}

	@Override
	protected void putException(String cacheKey, Throwable e, long timeOut) {
		log.debug("异常写入本地缓存cacheKey: {}", cacheKey);
		if (e == null) {
			cacheTimeOut.put(cacheKey, 200 + System.currentTimeMillis());
			cache.put(cacheKey, SerializationUtils.serialize(new Relust<Object>(e)));
		}
	}

	@Override
	protected void remove(String cacheKey) {
		log.debug("从本地缓存移除cacheKey: {}", cacheKey);
		cache.remove(cacheKey);
		cacheTimeOut.remove(cacheKey);
	}

	@Override
	protected void clear() {
		log.debug("清空本地缓存 ");
		cache.clear();
		invokLock.clear();
		cacheTimeOut.clear();
	}

	private int clearTimeCache() {
		int clearNum = 0;
		log.debug("开始清理过期缓存");
		/**
		 * 每次最多清理500个缓存
		 */
		Iterator<Entry<String, byte[]>> it = cache.entrySet().iterator();
		while (it.hasNext()) {
			Entry<String, byte[]> entry = it.next();
			String cacheKey = entry.getKey();
			Long timeOut;
			if (cacheTimeOut.containsKey(cacheKey) && (timeOut = cacheTimeOut.get(cacheKey)) != null
					&& timeOut > System.currentTimeMillis()) {
				continue;
			}
			log.debug("缓存{}已过期", cacheKey);
			it.remove();
			cacheTimeOut.remove(cacheKey);
		}
		if (clearNum > 0) {
			log.info("成功清理{}个过期缓存", clearNum);
		}
		return clearNum;
	}

	/**
	 * 用于同步数据时
	 * 
	 * @param cacheKey
	 */
	public void del(String cacheKey) {
		cacheTimeOut.remove(cacheKey);
		cache.remove(cacheKey);
	}
}
